/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 */
public final class Beans {

    private Beans() {
    }

    private static final String GET_PREFIX = "get";
    private static final String SET_PREFIX = "set";
    private static final String IS_PREFIX = "is";

    /**
     * 设置bean对象的可写属性值，相当于调用属性对应的setter方法
     *
     * @param bean
     * @param propertyName
     * @param value
     * @throws Exception
     */
    public static void setPropertyValue(Object bean, String propertyName, Object value) throws Exception {
        Checks.verifyNotNull(bean, "bean");
        Checks.verifyNotNull(propertyName, "propertyName");
        Class<?> classType = bean.getClass();
        Class<?> paramType;
        if (value != null) {
            paramType = value.getClass();
        } else {
            Method getter = findGetter(classType, propertyName);
            if (getter == null) {
                throw new NoSuchMethodException("set" + capitalize(propertyName) + "(Void)");
            }
            paramType = getter.getReturnType();
        }
        Method setter = findSetter(classType, propertyName, paramType);
        if (setter == null) {
            throw new NoSuchMethodException("set" + capitalize(propertyName) + "(" + paramType + ")");
        }
        setter.invoke(bean, value);
    }

    /**
     * 获取bean对象的可读属性值，相当于调用属性对应的getter方法
     *
     * @param bean
     * @param propertyName 属性名称
     * @return
     * @throws NoSuchMethodException
     */
    public static Object getPropertyValue(Object bean, String propertyName) throws Exception {
        Checks.verifyNotNull(bean, "bean");
        Checks.verifyNotNull(propertyName, "propertyName");
        Class<?> classType = bean.getClass();
        Method method = findGetter(classType, propertyName);
        if (method == null) {
            throw new NoSuchMethodException();
        }
        return method.invoke(bean, (Object[]) null);
    }

    /**
     * 返回包含指定对象的所有可读属性及值的Map，属性名称为key，属性值为value。
     * 返回的Map中只包含有getter方法的属性。
     *
     * @param bean
     * @return
     */
    public static Map<String, Object> getPropertiesValues(Object bean) {
        Checks.verifyNotNull(bean, "bean");
        HashMap<String, Object> result = new HashMap<>();
        Class<?> clazz = bean.getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String fieldName = null;
            String methodName = method.getName();
            if (methodName.startsWith(GET_PREFIX)) {
                fieldName = methodName.substring(3);
            } else if (methodName.startsWith(IS_PREFIX) && boolean.class != method.getReturnType()) {
                fieldName = methodName.substring(2);
            }
            if (fieldName != null) {
                fieldName = uncapitalize(fieldName);
                Field field = findField(clazz, fieldName);
                if (field != null) {
                    try {
                        Object value = method.invoke(bean, (Object[]) null);
                        result.put(fieldName, value);
                    } catch (Exception e) {
                        // ignore
                    }
                }
            }
        }
        return result;
    }

    /**
     *
     * @return
     */
    public static BeanCopier copier() {
        return new BeanCopier();
    }

    /**
     * 将一个对象的属性值赋给另一个对象的相同的属性 这两个对象必须都符合javaBean的标准
     *
     * @param source 要赋值的源对象
     * @param target 被赋值的目标对象
     * @param ignoreProperties 被忽略赋值的属性数组
     * @throws Exception
     *
     */
    public static void copyProperties(Object source, Object target, String... ignoreProperties) throws Exception {
        copyProperties(source, target, null, ignoreProperties);
    }

    /**
     * 将一个对象的属性值赋给另一个对象的相同的属性 这两个对象必须都符合javaBean的标准
     * @param source
     * @param target
     * @param source 要赋值的源对象
     * @param target 被赋值的目标对象
     * @param converter
     * @param ignoreProperties 被忽略赋值的属性数组
     * @throws Exception
     */
    public static void copyProperties(Object source,
                                       Object target,
                                       FieldConverter converter,
                                       String... ignoreProperties) throws Exception {
        Checks.verifyNotNull(source, "source");
        Checks.verifyNotNull(target, "target");
        Set<String> ignores = new HashSet<>();
        Collections.addAll(ignores, ignoreProperties);
        HashMap<String, Method> targetMethodMap = new HashMap<>();
        Class<?> targetClass = target.getClass();
        for (Method method : targetClass.getMethods()) {
            if (method.getParameterTypes().length != 1) {
                continue;
            }
            String fieldName = null;
            String methodName = method.getName();
            if (methodName.startsWith(SET_PREFIX)) {
                fieldName = methodName.substring(3);
            }
            if (fieldName != null && !ignores.contains(uncapitalize(fieldName))) {
                targetMethodMap.put(fieldName, method);
            }
        }
        Class<?> sourceClass = source.getClass();
        for (Method method : sourceClass.getMethods()) {
            String fieldName = null;
            String methodName = method.getName();
            if (methodName.startsWith(GET_PREFIX)) {
                fieldName = methodName.substring(3);
            } else if (methodName.startsWith(IS_PREFIX) && boolean.class != method.getReturnType()) {
                fieldName = methodName.substring(2);
            }
            if (fieldName != null) {
                Method setter = targetMethodMap.get(fieldName);
                if (setter != null) {
                    try {
                        Class<?> setterClass = setter.getParameterTypes()[0];
                        Object value = method.invoke(source);
                        if (value != null && !setterClass.isInstance(value) && converter != null) {
                            value = converter.convert(value, method, setter);
                        }
                        setter.invoke(target, value);
                    } catch (Exception ex) {
                        throw new IllegalArgumentException("Could not copy property from source to target."
                                + uncapitalize(fieldName), ex);
                    }
                }
            }
        }
    }

    /**
     * 返回指定属性的getter方法
     *
     * @param classType
     * @param propertyName
     * @return
     */
    private static Method findGetter(Class<?> classType, String propertyName) {
        propertyName = capitalize(propertyName);
        Method method = null;
        try {
            method = classType.getMethod(GET_PREFIX + propertyName, (Class[]) null);
        } catch (NoSuchMethodException ex) {
            // ignroe
        }
        if (method == null) {
            try {
                method = classType.getMethod(IS_PREFIX + propertyName, (Class[]) null);
                Class<?> returnType = method.getReturnType();
                if (boolean.class != returnType) {
                    method = null;
                }
            } catch (NoSuchMethodException ex) {
                // ignroe
            }
        }
        return method;
    }

    /**
     * 返回指定属性的setter方法
     *
     * @param classType
     * @param propertyName
     * @param paramType
     * @return
     */
    private static Method findSetter(Class<?> classType, String propertyName, Class<?> paramType) {
        propertyName = capitalize(propertyName);
        Method method = null;
        try {
            method = classType.getMethod(SET_PREFIX + propertyName, paramType);
        } catch (NoSuchMethodException ex) {
            // ignroe
        }
        return method;
    }

    private static Field findField(Class<?> clazz, String fieldName) {
        if (clazz == Object.class) {
            return null;
        }
        try {
            Field field = clazz.getDeclaredField(fieldName);
            if (field != null) {
                return field;
            }
        } catch (Exception ex) {
            // ignroe
        }
        return findField(clazz.getSuperclass(), fieldName);
    }

    /**
     * 获取属性名称对应的不含set、get、is前缀的方法名称(按JavaBean规范)。与{@link #uncapitalize(String)}互反
     *
     * @param name
     * @return
     */
    public static String capitalize(String name) {
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1))) {
            return name;
        } else if (name.length() > 1) {
            return Character.toUpperCase(name.charAt(0)) + name.substring(1);
        } else {
            return name.toUpperCase();
        }
    }

    /**
     * 获取与不含set、get、is前缀的方法名称对应的属性(按JavaBean规范)。与{@link #capitalize(String)}互反
     *
     * @param name
     * @return
     */
    public static String uncapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1))
                && Character.isUpperCase(name.charAt(0))) {
            return name;
        }
        char[] chars = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }

    public static boolean isAccessorPresent(String prefix, String property, Class<?> bean) {
        try {
            bean.getMethod(prefix + capitalize(property));
            return true;
        } catch (NoSuchMethodException ex) {
            return false;
        }
    }

    public static Method getAccessor(String prefix, String property, Class<?> bean) {
        try {
            return bean.getMethod(prefix + capitalize(property));
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public static class BeanCopier {

        private final List<ConverterHolder<?, ?>> converterHolders;

        private final FieldConverter fieldConverter = new FieldConverter() {
            @Override
            public Object convert(Object sourceValue, Method sourceMethod, Method targetMethod) {
                for (int i = converterHolders.size() - 1; i >= 0; i--) {
                    ConverterHolder<?, ?> holder = converterHolders.get(i);
                    if (holder.isMatching(sourceMethod, targetMethod)) {
                        return holder.convert(sourceValue);
                    }
                }
                return sourceValue;
            }
        };

        private BeanCopier() {
            this.converterHolders = new ArrayList<>();
        }

        public void addConverterHolder(ConverterHolder<?, ?> holder) {
            converterHolders.add(holder);
        }

        public <S> SourceHolder<S> from(Class<S> sourceClass) {
            Checks.verifyNotNull(sourceClass, "sourceClass");
            return new SourceHolder<>(this, sourceClass);
        }

        /**
         * 支持将 {@link Enume} 类型实例转为值
         * @return
         */
        public BeanCopier fromEnume() {
            from(Enume.class).to(Comparable.class).with(new EnumeToValueConverter());
            return this;
        }

        /**
         * 支持将值转为 {@link Enume} 类型实例
         * @param enumeClass
         * @return
         */
        public <V extends Comparable<V>, E extends Enume<V>> BeanCopier toEnume(Class<E> enumeClass) {
            addConverterHolder(new EnumeConverterHolder(Enume.valueClass(enumeClass), enumeClass));
            return this;
        }

        /**
         * 支持将值转为 {@link Enume} 类型实例
         * @param enumeClasses
         * @return
         */
        public BeanCopier toEnumes(Collection<Class<? extends Enume<?>>> enumeClasses) {
            for (Class<? extends Enume<?>> enumeClass : enumeClasses) {
                addConverterHolder(new EnumeConverterHolder(Enume.valueClass(enumeClass), enumeClass));
            }
            return this;
        }

        public void copy(Object source, Object target, String... ignoreProperties) throws Exception {
            copyProperties(source, target, fieldConverter, ignoreProperties);
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public static class SourceHolder<S> {

        private final BeanCopier copier;
        private final Class<S> sourceClass;

        private SourceHolder(BeanCopier copier, Class<S> sourceClass) {
            this.copier = copier;
            this.sourceClass = sourceClass;
        }

        public <T> TargetHolder<S, T> to(Class<T> targetClass) {
            Checks.verifyNotNull(targetClass, "targetClass");
            return new TargetHolder<>(copier, sourceClass, targetClass);
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public static class TargetHolder<S, T> {

        private final BeanCopier copier;
        private final Class<S> sourceClass;
        private final Class<T> targetClass;

        private TargetHolder(BeanCopier copier, Class<S> sourceClass, Class<T> targetClass) {
            this.copier = copier;
            this.sourceClass = sourceClass;
            this.targetClass = targetClass;
        }

        public BeanCopier with(Converter<S, T> converter) {
            Checks.verifyNotNull(converter, "converter");
            copier.addConverterHolder(new ConverterHolder<>(sourceClass, targetClass, converter));
            return copier;
        }

    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public static class ConverterHolder<S, T> {

        private final Class<S> sourceClass;
        private final Class<T> targetClass;
        private final Converter<S, T> converter;

        public ConverterHolder(Class<S> sourceClass, Class<T> targetClass, Converter<S, T> converter) {
            this.sourceClass = sourceClass;
            this.targetClass = targetClass;
            this.converter = converter;
        }

        protected boolean isMatching(Method sourceMethod, Method targetMethod) {
            Class<?> sourceType = sourceMethod.getReturnType();
            Class<?> targetType = targetMethod.getParameterTypes()[0];
            return sourceClass.isAssignableFrom(sourceType) && targetClass.isAssignableFrom(targetType);
        }

        protected Object convert(Object source) {
            return converter.convert((S) source);
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    private static class EnumeConverterHolder<V extends Comparable<V>, E extends Enume<V>> extends ConverterHolder<V, E> {

        public EnumeConverterHolder(Class<V> sourceClass, Class<E> targetClass) {
            super(sourceClass, targetClass, new ValueToEnumeConverter<>(targetClass));
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    private static class EnumeToValueConverter implements Beans.Converter<Enume, Comparable> {

        private EnumeToValueConverter() {
        }

        @Override
        public Comparable convert(Enume source) {
            return source.value();
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    private static class ValueToEnumeConverter<V extends Comparable<V>, E extends Enume<V>> implements Beans.Converter<V, E> {

        private final Class<E> enumeClass;

        private ValueToEnumeConverter(Class<E> enumeClass) {
            this.enumeClass = enumeClass;
        }

        @Override
        public E convert(V source) {
            return Enume.fromValue(enumeClass, source);
        }
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public interface Converter<S, T> {

        T convert(S source);
    }

    /**
     *
     * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
     */
    public interface FieldConverter {

        Object convert(Object sourceValue, Method sourceMethod, Method targetMethod);

    }

}
